﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Young3.FMSearch.Core.Entities.GameVersions;
using Young3.FMSearch.Core.Entities.InGame;
using Young3.FMSearch.Core.Entities;
using Young3.FMSearch.Core.Attributes;
using System.Reflection;
using Young3.FMSearch.Core.Offsets;
using System.Linq;
using System.Linq.Expressions;
using System.Diagnostics;

namespace Young3.FMSearch.Core.Managers
{
    internal static class ObjectManagerWrapper
    {
        public static Dictionary<DatabaseModeEnum, ObjectManager> ObjectManagers =
            new Dictionary<DatabaseModeEnum, ObjectManager>();

        public static StaffMemoryAddressesWrapper StaffMemoryCache = null;
    }

    internal class ObjectManager
    {
        /// <summary>
        /// The objectstore contains every object in the game
        /// </summary>
        public Dictionary<Type, object> ObjectStore = new Dictionary<Type, object>();

        private const int BufferSize = 8*1024*1024; // buffer size in bytes
        private const int BufferSizeStaff = 16*1024*1024;

        public readonly DatabaseModeEnum DatabaseMode;
        public readonly GameManager GameManager;

        public ObjectManager(GameManager gameManager, DatabaseModeEnum databaseMode)
        {
            DatabaseMode = databaseMode;
            GameManager = gameManager;

            if (!ObjectManagerWrapper.ObjectManagers.ContainsKey(databaseMode))
            {
                ObjectManagerWrapper.ObjectManagers.Add(databaseMode, null);
            }
            ObjectManagerWrapper.ObjectManagers[databaseMode] = this;
        }

        /// <summary>
        /// Fills the objectstore with all ingame objects
        /// </summary>
        public void Load(bool refreshPersonCache)
        {
            ObjectStore.Clear();
            Stopwatch sw = new Stopwatch(); sw.Start();

            if(ObjectManagerWrapper.StaffMemoryCache == null && !refreshPersonCache)
            {
                ObjectManagerWrapper.StaffMemoryCache = GetPersonMemoryAddresses(BufferSizeStaff);
            }
            var staffAddresses = ObjectManagerWrapper.StaffMemoryCache;

            // doing the m=>m.XXX twice, is because we don't have to manual compile the delegate. Faster that way.

            ObjectStore.Add(typeof(City), RetrieveObjects<City>(m => m.City, m => m.City));
            ObjectStore.Add(typeof(Continent), RetrieveObjects<Continent>(m => m.Continent,m=>m.Continent));
            ObjectStore.Add(typeof(Country), RetrieveObjects<Country>(m => m.Country, m=>m.Country));
            ObjectStore.Add(typeof(Stadium), RetrieveObjects<Stadium>(m => m.Stadium, m=>m.Stadium));
            ObjectStore.Add(typeof(League), RetrieveObjects<League>(m => m.League, m=>m.League));
            ObjectStore.Add(typeof(Team), RetrieveObjects<Team>(m => m.Team, m => m.Team));
            ObjectStore.Add(typeof(Club), RetrieveObjects<Club>(m => m.Club, m => m.Club));
            ObjectStore.Add(typeof(HumanManager), RetrieveObjects<HumanManager>(staffAddresses.HumanManagersAddresses));

            ObjectStore.Add(typeof(Player), RetrieveObjects<Player>(staffAddresses.PlayerAddresses));
            ObjectStore.Add(typeof(Staff), RetrieveObjects<Staff>(staffAddresses.StaffAddresses));

            if (this.DatabaseMode == DatabaseModeEnum.Cached)
            {
                Stopwatch preLoad = new Stopwatch(); preLoad.Start();
                var playerSkillKeys = ((Dictionary<int, Player>)ObjectStore[typeof(Player)]).Values.Select(k => k.SkillsAddress).AsParallel().ToList();
                var playerRelationKeys = ((Dictionary<int, Player>)ObjectStore[typeof(Player)]).Values.Select(k => k.RelationsAddress).AsParallel().ToList();

                var staffSkillKeys = ((Dictionary<int, Staff>)ObjectStore[typeof(Staff)]).Values.Select(k => k.SkillsAddress).AsParallel().ToList();

                var financeKeys = ((Dictionary<int, Club>)ObjectStore[typeof(Club)]).Values.Select(k => k.FinanceAddress).AsParallel().ToList();

                //fix object contracts
                List<int> contractKeys =new List<int>();
                FixObjectsFromPointer(out contractKeys);

                preLoad.Stop(); Debug.WriteLine("Preloaded keys in " + preLoad.ElapsedMilliseconds + " ms");
                //needed for late binding of objectpointers
                ObjectStore.Add(typeof(PlayerSkills), RetrieveObjectsCached<PlayerSkills>(m => m.CurrentDateTime, m => m.CurrentDateTime, playerSkillKeys));
                ObjectStore.Add(typeof(PlayerRelationContainer), RetrieveObjectsCached<PlayerRelationContainer>(m => m.CurrentDateTime, m => m.CurrentDateTime, playerRelationKeys));
                ObjectStore.Add(typeof(StaffSkills), RetrieveObjectsCached<StaffSkills>(m => m.CurrentDateTime, m => m.CurrentDateTime, staffSkillKeys));
                ObjectStore.Add(typeof(PositionalSkills), RetrieveObjectsCached<PositionalSkills>(m => m.CurrentDateTime, m => m.CurrentDateTime, playerSkillKeys));
                ObjectStore.Add(typeof(NewPositionalSkills), RetrieveObjectsCached<NewPositionalSkills>(m => m.CurrentDateTime, m => m.CurrentDateTime, playerSkillKeys));
                ObjectStore.Add(typeof(Contract), RetrieveObjectsCached<Contract>(m => m.CurrentDateTime, m => m.CurrentDateTime, contractKeys));
                ObjectStore.Add(typeof(Finance), RetrieveObjectsCached<Finance>(m => m.CurrentDateTime, m => m.CurrentDateTime, financeKeys));

                Stopwatch filter = new Stopwatch(); filter.Start();
                //filter players
                var plToRemove = from p in (Dictionary<int, Player>)ObjectStore[typeof(Player)]
                                 select p.Value as Player into p2
                                 where !((Dictionary<int, PositionalSkills>)ObjectStore[typeof(PositionalSkills)]).ContainsKey(p2.SkillsAddress)
                                 select p2.MemoryAddress;
                foreach (var p in plToRemove.ToList())
                    ((Dictionary<int, Player>)ObjectStore[typeof(Player)]).Remove(p);

                //filter staff
                var stToRemove = from p in (Dictionary<int, Staff>)ObjectStore[typeof(Staff)]
                                 select p.Value as Staff into p2
                                 where !((Dictionary<int, StaffSkills>)ObjectStore[typeof(StaffSkills)]).ContainsKey(p2.SkillsAddress)
                                 select p2.MemoryAddress;
                foreach (var p in stToRemove.ToList())
                    ((Dictionary<int, Staff>)ObjectStore[typeof(Staff)]).Remove(p);
                filter.Stop(); Debug.WriteLine("Filtering players/staff in " + filter.ElapsedMilliseconds + " ms");
            }

            sw.Stop(); Debug.WriteLine("Loaded gamedata in " + sw.ElapsedMilliseconds + " ms");
        }

        private void FixObjectsFromPointer(out List<int> newContractAddresses)
        {
            // the reason we don't use Linq for players here, is that we want to have a list
            // that is sorted as much as possible. Sorting is way more intensive if the list is all
            // over the place.

            List<int> playerContractAddresses = new List<int>();
            foreach(var p in (((Dictionary<int, Player>)ObjectStore[typeof (Player)]).Values))
            {
                if (p.ContractAddress <= 0x00) continue;

                playerContractAddresses.Add(p.ContractAddress);
                if (p.ContractNumber == 2) playerContractAddresses.Add(p.ContractAddress + 0x04);
            }
            playerContractAddresses = playerContractAddresses.Distinct().ToList();

            List<int> staffContractAddresses = new List<int>();
            foreach (var p in (((Dictionary<int, Staff>)ObjectStore[typeof(Staff)]).Values))
            {
                if (p.ContractAddress <= 0x00) continue;
                staffContractAddresses.Add(p.ContractAddress);
            }
            staffContractAddresses = staffContractAddresses.Distinct().ToList();
            
            Dictionary<int, int> outputList = new Dictionary<int, int>();
            object outputListSyncroot = new object();

            List<List<int>> memoryAddressBatches = SplitMemoryAddressesIntoBuffer(playerContractAddresses, 16 * 1024 * 1024, false);
            memoryAddressBatches.Union(SplitMemoryAddressesIntoBuffer(staffContractAddresses, 16 * 1024 * 1024, false));

            foreach (List<int> memoryBatch in memoryAddressBatches.Where(m => m.Count > 0))
            {
                int max = memoryBatch.Max();
                int min = memoryBatch.Min();

                int dataBufferSize = max - min + Convert.ToInt32(0x04); // all 4 byte thingy's
                byte[] dataBuffer = ProcessManager.ReadProcessMemory(min, Convert.ToUInt32(dataBufferSize));
                int lowestPointerInBatch = min;

                for (int i = 0; i < memoryBatch.Count; i++)
                {
                    ArraySegment<byte> cutBuffer;
                    try
                    {
                        cutBuffer = CutBytes(dataBuffer, memoryBatch[i] - lowestPointerInBatch, 0x04);
                    }
                    catch (IndexOutOfRangeException)
                    {
                        break;
                    }

                    var obj = ProcessManager.ReadInt32(cutBuffer.Array, cutBuffer.Offset);

                    // hack to remove unwanted addresses (negative, zero, etc)
                    if (obj <= 0) break;

                    
                        try
                        {
                            outputList.Add(memoryBatch[i], obj);
                        }
                        catch{}
                }
            }

            newContractAddresses = outputList.Values.ToList();

            foreach (Player player in ((Dictionary<int, Player>)ObjectStore[typeof(Player)]).Values.AsParallel())
            {
                int offset = player.OriginalBytes.Offset;

                int contractAddress = player.ContractAddress;
                if (!outputList.ContainsKey(contractAddress)) continue;
                int value = outputList[contractAddress];

                player.OriginalBytes.Array[offset + PlayerOffsets.ContractAddress + 0] = (byte)(value & 0xff);
                player.OriginalBytes.Array[offset + PlayerOffsets.ContractAddress + 1] = (byte)((value & 0xff00) >> 8);
                player.OriginalBytes.Array[offset + PlayerOffsets.ContractAddress + 2] = (byte)((value & 0xff0000) >> 0x10);
                player.OriginalBytes.Array[offset + PlayerOffsets.ContractAddress + 3] = (byte)((value & 0xff000000L) >> 0x18);

                if (!outputList.ContainsKey(contractAddress + 4)) continue;
                value = outputList[contractAddress + 4];
                player.OriginalBytes.Array[offset + PlayerOffsets.ContractAddressSecond + 0] = (byte)(value & 0xff);
                player.OriginalBytes.Array[offset + PlayerOffsets.ContractAddressSecond + 1] = (byte)((value & 0xff00) >> 8);
                player.OriginalBytes.Array[offset + PlayerOffsets.ContractAddressSecond + 2] = (byte)((value & 0xff0000) >> 0x10);
                player.OriginalBytes.Array[offset + PlayerOffsets.ContractAddressSecond + 3] = (byte)((value & 0xff000000L) >> 0x18);
            }

            foreach (Staff staff in ((Dictionary<int, Staff>)ObjectStore[typeof(Staff)]).Values.AsParallel())
            {
                int offset = staff.OriginalBytes.Offset;
                int contractAddress = staff.ContractAddress;
                if (!outputList.ContainsKey(contractAddress)) continue;
                int value = outputList[contractAddress];

                staff.OriginalBytes.Array[offset + StaffOffsets.ContractPointer + 0] = (byte)(value & 0xff);
                staff.OriginalBytes.Array[offset + StaffOffsets.ContractPointer + 1] = (byte)((value & 0xff00) >> 8);
                staff.OriginalBytes.Array[offset + StaffOffsets.ContractPointer + 2] = (byte)((value & 0xff0000) >> 0x10);
                staff.OriginalBytes.Array[offset + StaffOffsets.ContractPointer + 3] = (byte)((value & 0xff000000L) >> 0x18);
            }
        }

        /// <summary>
        /// Retrieves personAddresses that are now in one big array, and need to
        /// be splitted
        /// </summary>
        /// <returns>Struct containing splitted personAddresses</returns>
        private StaffMemoryAddressesWrapper GetPersonMemoryAddresses(int maxBufferSize)
        {
            object memAddrSyncroot = new object();

            Stopwatch loadStaff = new Stopwatch(); loadStaff.Start();

            StaffMemoryAddressesWrapper memoryAddresses = new StaffMemoryAddressesWrapper();
            memoryAddresses.PlayerAddresses = new List<int>();
            memoryAddresses.PlayerStaffAddresses = new List<int>();
            memoryAddresses.HumanManagersAddresses = new List<int>();
            memoryAddresses.StaffAddresses = new List<int>();

            //Dictionary<int, int> test = new Dictionary<int, int>();

            List<int> addresses = GetMemoryAddresses(m=>m.Person, m=>m.Person);

            List<List<int>> memoryAddressBatches = SplitMemoryAddressesIntoBuffer(addresses, maxBufferSize, false);
            
            foreach (var memoryBatch in memoryAddressBatches.AsParallel())
            {
                byte[] buffer = ProcessManager.ReadProcessMemory(memoryBatch.Min(), Convert.ToUInt32(memoryBatch.Max() + 2000 - memoryBatch.Min()));
                int lowestPointerInBatch = memoryBatch.Min();

                foreach (int memoryAddress in memoryBatch)
                {
                    //if (memoryAddress > 0x1CD71470 - 0x1000 && memoryAddress < 0x1CD71470 + 0x1000)
                    //{
                        
                    //}
                //first 4 bytes contain the type
                    int type = ProcessManager.ReadInt32(buffer, memoryAddress + 0x0 - lowestPointerInBatch);

                    lock (memAddrSyncroot)
                    {
                        if (type == GameManager.Version.PersonEnum.Player)
                        {
                            memoryAddresses.PlayerAddresses.Add(memoryAddress + 0xF8);
                        }
                        else if (type == GameManager.Version.PersonEnum.Staff)
                        {
                            memoryAddresses.StaffAddresses.Add(memoryAddress + 0xF8);
                        }
                        else if (type == GameManager.Version.PersonEnum.HumanManager)
                        {
                            memoryAddresses.HumanManagersAddresses.Add(memoryAddress - 0x44);
                        }
                        else if (type == GameManager.Version.PersonEnum.PlayerStaff)
                        {
                            memoryAddresses.PlayerStaffAddresses.Add(memoryAddress + 0xF8); // don't wanna know
                        }
                    }
                }
            }

            loadStaff.Stop(); Debug.WriteLine("Loaded staff memory addresses (buffer: " +maxBufferSize + ") in " + loadStaff.ElapsedMilliseconds +  " ms");

            return memoryAddresses;
        }

        private Dictionary<int, T> RetrieveObjects<T>(Expression<Func<IVersionMemoryAddresses, int>> baseObjectAddress, Func<IVersionMemoryAddresses, int> compiledObjectPointer)
            where T : BaseObject
        {
            return RetrieveObjects<T>(baseObjectAddress, compiledObjectPointer, null);
        }

        private Dictionary<int, T> RetrieveObjects<T>(List<Int32> memoryAddresses)
            where T : BaseObject
        {
            return RetrieveObjects<T>(m=>m.CurrentDateTime, m=>m.CurrentDateTime, memoryAddresses);
        }

        /// <summary>
        /// Loads all objects of a certain kind into the ObjectStore
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="baseObjectAddress"></param>
        /// <returns></returns>
        private Dictionary<int, T> RetrieveObjects<T>(Expression<Func<IVersionMemoryAddresses, int>> baseObjectPointer, Func<IVersionMemoryAddresses, int> compiledObjectPointer, List<Int32> MemoryAddresses)
            where T : BaseObject
        {
            if (this.DatabaseMode == DatabaseModeEnum.Cached) return RetrieveObjectsCached<T>(baseObjectPointer, compiledObjectPointer, MemoryAddresses);
            else return RetrieveObjectsRealtime<T>(baseObjectPointer, compiledObjectPointer, MemoryAddresses);
        }

        private Dictionary<int, T> RetrieveObjectsRealtime<T>(Expression<Func<IVersionMemoryAddresses, int>> baseObjectPointer, Func<IVersionMemoryAddresses, int> compiledObjectPointer, List<Int32> MemoryAddresses)
           where T : BaseObject
        {
            Stopwatch processingItems = new Stopwatch(); processingItems.Start();

            if (MemoryAddresses == null)
                MemoryAddresses = GetMemoryAddresses(baseObjectPointer, compiledObjectPointer);

            #region CreateConstructorDelegate
            //create a NewExpression for speed
            ConstructorInfo constructor = typeof(T).GetConstructor(new[] { typeof(int), typeof(IVersion) });
            ParameterExpression expPointer = Expression.Parameter(typeof(Int32), "memoryAddress");
            ParameterExpression vPointer = Expression.Parameter(typeof(IVersion), "version");
            Expression createNew = Expression.New(constructor, expPointer, vPointer);
            LambdaExpression lambda = Expression.Lambda(createNew, new[] { expPointer, vPointer });
            Func<int, IVersion, T> constructInvoker = (Func<int,IVersion, T>)lambda.Compile();
            #endregion

            var outputList = new Dictionary<int, T>(MemoryAddresses.Count);

            foreach (int address in MemoryAddresses)
            {
                var obj = constructInvoker.Invoke(address, GameManager.Version);
                try
                {
                    outputList.Add(address, obj);
                }
                catch { }
            }

            processingItems.Stop();
            Debug.WriteLine("Processed " + outputList.Count + " items of type " + typeof(T).Name + " in " + processingItems.Elapsed.TotalMilliseconds.ToString("F2") + " ms");

            return outputList;
        }

        private Dictionary<int, T> RetrieveObjectsCached<T>(Expression<Func<IVersionMemoryAddresses, int>> baseObjectPointer, Func<IVersionMemoryAddresses, int> compiledObjectPointer, List<Int32> MemoryAddresses)
            where T: BaseObject
        {
            int lowestPointer, highestPointer;
            int maxBufferSize = BufferSize;
            Dictionary<int, T> outputList = new Dictionary<int, T>();
            object outputListSyncroot = new object();

            Stopwatch processingItems = Stopwatch.StartNew();

            if (MemoryAddresses == null)
                MemoryAddresses = GetMemoryAddresses(baseObjectPointer, compiledObjectPointer);
            else
            {
                if (MemoryAddresses.Count == 0) return outputList;

                MemoryAddresses = MemoryAddresses.Where(m => m > 0).ToList();
            }

            //for caching purposes
            lowestPointer = MemoryAddresses.Min();
            highestPointer = MemoryAddresses.Max();

            int objectSize = GetObjectSize(MemoryAddresses, lowestPointer);

            #region CreateConstructorDelegate
            //create a NewExpression for speed
            ConstructorInfo constructor = typeof(T).GetConstructor(new[] { typeof(int), typeof(ArraySegment<byte>), typeof(IVersion) });
            ParameterExpression expPointer = Expression.Parameter(typeof(Int32), "memoryAddress");
            ParameterExpression expBuffer = Expression.Parameter(typeof(ArraySegment<byte>), "originalBytes");
            ParameterExpression expVersion = Expression.Parameter(typeof(IVersion), "version");
            Expression createNew = Expression.New(constructor, expPointer, expBuffer, expVersion);
            LambdaExpression lambda = Expression.Lambda(createNew, new[] { expPointer, expBuffer, expVersion });
            var constructInvoker = (Func<int, ArraySegment<byte>, IVersion, T>)lambda.Compile();
            #endregion

            List<List<int>> memoryAddressBatches = SplitMemoryAddressesIntoBuffer(MemoryAddresses, maxBufferSize, false);

            if (MemoryAddresses.Count > 4000)
            {
                memoryAddressBatches = memoryAddressBatches.Where(m => m.Count > 1
                    && m.Count(q=>q>ProcessManager.FMProcess.EndPoint) == 0).ToList();
            }

            #region ProcessItemsIntoMemory
            foreach(List<int> memoryBatch in memoryAddressBatches.Where(m=>m.Count > 0))
            {
                int max = memoryBatch.Max();
                int min = memoryBatch.Min();

                int dataBufferSize = max - min + Convert.ToInt32(objectSize);
                byte[] dataBuffer = ProcessManager.ReadProcessMemory(min, Convert.ToUInt32(dataBufferSize));
                int lowestPointerInBatch = min;

                for(int i = 0; i < memoryBatch.Count; i++)
                //Parallel.For(0, memoryBatch.Count, i =>
                    {
                        if (outputList.ContainsKey(memoryBatch[i])) continue;

                        ArraySegment<byte> cutBuffer;
                        try {
                            cutBuffer = CutBytes(dataBuffer, memoryBatch[i] - lowestPointerInBatch, objectSize); 
                        }
                        catch (IndexOutOfRangeException) { break; }

                        var obj = constructInvoker.Invoke(memoryBatch[i], cutBuffer, GameManager.Version);

                        try
                        {
                            //lock (outputListSyncroot)
                            outputList.Add(memoryBatch[i], obj);
                        }
                        catch { }
                    }//);
            }
            processingItems.Stop();
            Debug.WriteLine("Processed " + outputList.Count + " items of type " + typeof(T).Name + " in " + processingItems.Elapsed.TotalMilliseconds.ToString("F2") + " ms");
            #endregion

            return outputList;
        }

        //private class Chunk
        //{
        //    public int LBound { get; set; }
        //    public int UBound { get; set; }

        //    public List<int> Items { get; set; }
        //}

        //public static List<List<int>> SplitMemoryAddressesIntoBufferFaster(int[] memAddresses)
        //{
        //    Stopwatch sw = Stopwatch.StartNew();

        //    List<Chunk> chunks = new List<Chunk>();

        //    int drieMb = 3*1024*1024;
        //    int maxChunkSize = 17*1024*1024;

        //    foreach(var m in memAddresses)
        //    {
        //        var possible = chunks.FirstOrDefault(c => c.LBound >= (m - drieMb) && c.UBound <= (m + drieMb));

        //        if(possible == null)
        //        {
        //            // nieuwe chunk
        //            chunks.Add(new Chunk() { LBound = m, UBound = m, Items = new List<int>() { { m } } });
        //        }
        //        else
        //        {
        //            possible.Items.Add(m);

        //            if(m < possible.LBound)
        //            {
        //                possible.LBound = m;
        //            }
        //            else if(m > possible.UBound)
        //            {
        //                possible.UBound = m;
        //            }

        //            //if(possible.UBound - possible.LBound > maxChunkSize)
        //            //{
        //            //    int[] items = possible.Items.OrderBy(a => a).ToArray();

        //            //    var middle = possible.Items.Count / 2;

        //            //    int[] chunk1 = new int[middle];
        //            //    int[] chunk2 = new int[possible.Items.Count - middle];
        //            //    Array.Copy(items, chunk1, middle);
        //            //    Array.Copy(items, chunk2, chunk2.Length);

        //            //    chunks.Remove(possible);

        //            //    chunks.Add(new Chunk()
        //            //                   {
        //            //                       LBound = chunk1[0],
        //            //                       UBound = chunk1[chunk1.Length - 1],
        //            //                       Items = chunk1.ToList()
        //            //                   });
        //            //    chunks.Add(new Chunk()
        //            //    {
        //            //        LBound = chunk2[0],
        //            //        UBound = chunk2[chunk2.Length - 1],
        //            //        Items = chunk2.ToList()
        //            //    });
        //            //}
        //        }
        //    }

        //    //var jajaa= chunks.Select(c => c.Items).ToList();
        //    sw.Stop();
        //    Debug.WriteLine("MemToBuffer New: " + sw.ElapsedMilliseconds);

        //    var jajaa = chunks.Select(c => c.Items).ToList();

        //    return jajaa;
        //}

        public static List<List<int>> SplitMemoryAddressesIntoBuffer(List<Int32> memoryAddressesList, int maxBufferSize, bool alreadySorted)
        {
            var maxBytes = ProcessManager.FMProcess.EndPoint;

            int bitshift = 18;

            int[,] chunks = new int[maxBytes >> bitshift, 10000];
            int[] counter = new int[maxBytes >> bitshift];

            foreach (var m in memoryAddressesList)
            {
                if (m > maxBytes) continue;

                var c = m >> bitshift;
                chunks[c, counter[c]] = m;
                counter[c]++;
            }

            List<List<int>> r = new List<List<int>>();
            for (int c = 0; c < (maxBytes >> bitshift); c++)
            {
                if (counter[c] > 0)
                {
                    List<int> blah = new List<int>();
                    for (int i = 0; i < counter[c]; i++)
                    {
                        blah.Add(chunks[c, i]);
                    }
                    r.Add(blah.OrderBy(d=>d).ToList());
                }
            }

            chunks = null;
            counter = null;

            return r;
        }

        private static int GetObjectSize(int[] memoryAddresses, int lowestPointer)
        {
            int objectSize = 2000;
            try
            {
                objectSize = memoryAddresses[1] - lowestPointer;
            }
            catch
            {
            }
            finally
            {
                if (objectSize < 10) objectSize = 1000;
                if (objectSize > 2000) objectSize = 800;
                if (objectSize < 300) objectSize = 300;
            }
            return objectSize;
        }
        private static int GetObjectSize(List<Int32> memoryAddresses, int lowestPointer)
        {
            int objectSize = 2000;
            try
            {
                objectSize = memoryAddresses[1] - lowestPointer;
            }
            catch
            {
            }
            finally
            {
                if (objectSize < 10) objectSize = 1000;
                if (objectSize > 2000) objectSize = 800;
                if (objectSize < 300) objectSize = 300;
            }
            return objectSize;
        }

        private static Dictionary<string, MemoryAddressAttribute> memoryAttributeCache = new Dictionary<string, MemoryAddressAttribute>();
        //private static Dictionary<string, Func<IVersionMemoryAddresses, int>> delegatePointerCache = new Dictionary<string, Func<IVersionMemoryAddresses, int>>();
        private List<Int32> GetMemoryAddresses(Expression<Func<IVersionMemoryAddresses, int>> baseObjectPointer, Func<IVersionMemoryAddresses, int> compiledObjectPointer)
        {
            string key = baseObjectPointer.ToString();
            if(!memoryAttributeCache.ContainsKey(key))
            {
                memoryAttributeCache.Add(key, (MemoryAddressAttribute)GameManager.Version.MemoryAddresses.GetType().GetProperty(
                    ((MemberExpression)baseObjectPointer.Body).Member.Name).GetCustomAttributes(
                        typeof (MemoryAddressAttribute), false).First());
            }
            MemoryAddressAttribute memoryAttribute = memoryAttributeCache[key];

            //we use baseObjectPointer in this case
            int memoryAddress;

            //if(!delegatePointerCache.ContainsKey(key))
            //{
            //    delegatePointerCache.Add(key, baseObjectPointer.Compile());
            //}

            memoryAddress = ProcessManager.ReadInt32(compiledObjectPointer.Invoke(GameManager.Version.MemoryAddresses));
            if (memoryAttribute.BytesToSkip > 0)
                memoryAddress = ProcessManager.ReadInt32(memoryAddress + memoryAttribute.BytesToSkip);

            memoryAddress += memoryAttribute.ObjectOffset; // this has changed in 10.3 (was 0x04);

            int numberOfObjects = ProcessManager.ReadInt32(memoryAddress + memoryAttribute.CountLength);

            // 0x1cd71470

            // urm, dunno how we are going to do this, sorry :-)
            //if (baseObjectPointer == MemoryAddresses.Continent) numberOfObjects = 6;

            //we have a pointer here, so we do one last lookup
            memoryAddress = ProcessManager.ReadInt32(memoryAddress);

            List<int> memoryAddresses = GetMemoryAddresses(memoryAddress, numberOfObjects);

            return memoryAddresses;
        }

        public static List<Int32> GetMemoryAddresses(int firstAddress, int numberOfObjects)
        {
            object memListSyncroot = new object();

            byte[] pointerCache = ProcessManager.ReadProcessMemory(firstAddress, Convert.ToUInt32(numberOfObjects * 4));
            List<int> memoryAddresses = new List<int>(numberOfObjects);

            for (int i = 0; i < pointerCache.Length; i += 4)
            {
                int toAdd = ProcessManager.ReadInt32(pointerCache, i);

                if (toAdd != 0)
                {
                    memoryAddresses.Add(toAdd);
                }
            }
            return memoryAddresses;
        }

        private static ArraySegment<byte> CutBytes(byte[] buffer, int offset, int length)
        {
            return new ArraySegment<byte>(buffer, offset, length);
        }

        private static ConcurrentDictionary<Type, MethodInfo> _methodInfos = new ConcurrentDictionary<Type, MethodInfo>();
        public static MethodInfo GetMethodInfo(Type type)
        {
            Func<string, string> firstUp = (s) => s.Substring(0, 1).ToUpper() + s.Substring(1).ToLower();

            if (!_methodInfos.ContainsKey(type))
            {
                Type[] readTypes = new[] { typeof(byte[]), typeof(int) };

                if (type == typeof(string))
                    _methodInfos.TryAdd(type, typeof(ProcessManager).GetMethod("ReadString", new[] { typeof(byte[]), typeof(int), typeof(int) }));
                else
                    _methodInfos.TryAdd(type, typeof(ProcessManager).GetMethod("Read" + firstUp.Invoke(type.Name), readTypes));
            }
            return _methodInfos[type];
        }

        /// <summary>
        /// test finding strings
        /// </summary>
        /// <param name="mem"></param>
        private void CheckString(int mem, int aantalBytes = 400, int offset = 0x0)
        {
            var obj = new { MemoryAddress = mem };
            for (int i = obj.MemoryAddress; i < obj.MemoryAddress + aantalBytes; i++)
            {
                var s = ProcessManager.ReadString(i, null, offset, false);
                if (!string.IsNullOrEmpty(s))
                    Debug.WriteLine(string.Format("{0} ({2}): {1}", i, s, i - mem));
            }
        }

        /// <summary>
        /// test finding pointers
        /// </summary>
        /// <param name="mem"></param>
        /// <param name="start"></param>
        /// <param name="end"></param>
        private void Between(int mem, int start, int end)
        {
            var obj = new { MemoryAddress = mem };
            for (int i = obj.MemoryAddress; i < obj.MemoryAddress + 400; i++)
            {
                var s = ProcessManager.ReadInt32(i);
                if (s >= start && s <= end)
                    Debug.WriteLine(i - obj.MemoryAddress + ": " + s);
            }
        }
    }

    internal class StaffMemoryAddressesWrapper
    {
        public List<int> PlayerAddresses { get; set; }
        public List<int> StaffAddresses { get; set; }
        public List<int> PlayerStaffAddresses { get; set; }
        public List<int> HumanManagersAddresses { get; set; }
    }
}

